/*
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "NTree.h"
#include <string>
#include <fstream>
#include "AuxMath.h"
#include "Vector3i.h"

#pragma once

template<class T>
class DDS
{
private:
	typedef uint32_t DWORD;

	struct DDS_PIXELFORMAT {
		DWORD dwSize;
		DWORD dwFlags;
		DWORD dwFourCC;
		DWORD dwRGBBitCount;
		DWORD dwRBitMask;
		DWORD dwGBitMask;
		DWORD dwBBitMask;
		DWORD dwABitMask;
	};

	enum DDS_PF_FLAGS
	{
		DDPF_ALPHAPIXELS = 0x1,
		DDPF_ALPHA = 0x2,
		DDPF_FOURCC = 0x4,
		DDPF_RGB = 0x40,
		DDPF_YUV = 0x200,
		DDPF_LUMINANCE = 0x20000
	};

	struct DDS_HEADER{
		DWORD           dwSize;
		DWORD           dwFlags;
		DWORD           dwHeight;
		DWORD           dwWidth;
		DWORD           dwPitchOrLinearSize;
		DWORD           dwDepth;
		DWORD           dwMipMapCount;
		DWORD           dwReserved1[11];
		DDS_PIXELFORMAT ddspf;
		DWORD           dwCaps;
		DWORD           dwCaps2;
		DWORD           dwCaps3;
		DWORD           dwCaps4;
		DWORD           dwReserved2;
	};

	enum DDS_FLAGS
	{
		DDSD_CAPS=0x1,
		DDSD_HEIGHT=0x2,
		DDSD_WIDTH=0x4,
		DDSD_PITCH=0x8,
		DDSD_PIXELFORMAT=0x1000,
		DDSD_MIPMAPCOUNT=0x20000,
		DDSD_LINEARSIZE=0x80000,
		DDSD_DEPTH=0x800000
	};

	enum DDS_CAPS
	{
		DDSCAPS_COMPLEX=0x8,
		DDSCAPS_MIPMAP=0x400000,
		DDSCAPS_TEXTURE=0x1000
	};
	enum DDS_CAPS2
	{
		DDSCAPS2_VOLUME	= 0x200000
	};

	typedef unsigned char BYTE;


	static void
	setBits(BYTE* pbyData, const unsigned int uiBase, const unsigned int uiMultiSample,
			const Box<Vector3i>& box)
	{
		const unsigned int uiImageBase=uiBase/uiMultiSample;
		for (int z = box.min.z; z <= box.max.z; z++)
		{
			const unsigned int uiZOff=(z/uiMultiSample)*uiImageBase*uiImageBase;
			for (int y = box.min.y; y <= box.max.y; y++)
			{
				const unsigned int uiYOff=uiZOff+(y/uiMultiSample)*uiImageBase;
				for (int x = box.min.x; x <= box.max.x; x++)
				{
					const unsigned int uiXOff=uiYOff+(x/uiMultiSample);
					pbyData[uiXOff]++;
				}
			}
		}
	}

	static void
	rasterSubVoxels(const typename NTree<Vector3i, T>::Node& node,
				const Area<Vector3i>& area, BYTE* pbyData, unsigned int uiBase,
				const unsigned int uiMultiSample)
	{
		const Vector3i vShift(uiBase>>1);
		Box<Vector3i> areaBox=area.toBox();

		switch(node.getType())
		{
		case Nil:
			break;
		case Solid:
			//write box to dds
			setBits(pbyData, uiBase, uiMultiSample,
					Box<Vector3i>(areaBox.min+vShift, areaBox.max+vShift));
			break;
		default:
			Area<Vector3i> subAreas[Vector3i::childCount];
			area.subAreas(subAreas);
			for (unsigned int i = 0; i < Vector3i::childCount; i++)
				rasterSubVoxels(node.constGetSubNode(i), subAreas[i],
						pbyData, uiBase, uiMultiSample);
			break;
		}
	}

public:

	static bool
	treeToDDS(std::string filename, const NTree<Vector3i, T>& tree, const unsigned int uiMultiSample=1)
	{
		if(uiMultiSample > 6)
			return false;
		if(uiMultiSample < 1)
			return false;

		filename+=std::string(".dds");

		unsigned int uiBase=AuxMath::pow2(tree.getSize())*2;

		const unsigned int uiImageBase=uiBase/uiMultiSample;
		//reserve a bunch of mem
		unsigned int uiRawSize=uiImageBase*uiImageBase*uiImageBase*sizeof(BYTE);
		BYTE* pbyData=new BYTE[uiRawSize/sizeof(BYTE)];

		memset(pbyData, 0, uiRawSize);

		Area<Vector3i> subs[Vector3i::childCount];
		tree.subAreas(subs);

		for (unsigned int i = 0; i < Vector3i::childCount; i++)
			rasterSubVoxels(tree.constGetRoot(i), subs[i], pbyData, uiBase, uiMultiSample);

		//regenerate multi-sampled values
		unsigned int uiMultiSamples=uiMultiSample*uiMultiSample*uiMultiSample;
		float fScaler=255.0f/uiMultiSamples;
		for(unsigned int uiByte=0; uiByte < (uiRawSize/sizeof(BYTE)); uiByte++)
		{
			pbyData[uiByte]=(BYTE)((float)pbyData[uiByte]*fScaler);
		}


		DDS_HEADER ddsHead;
		memset(&ddsHead, 0, sizeof(DDS_HEADER));

		ddsHead.dwSize=sizeof(DDS_HEADER);//124
		ddsHead.dwFlags=DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT |
							DDSD_PIXELFORMAT | DDSD_DEPTH;
		ddsHead.dwWidth=ddsHead.dwHeight=ddsHead.dwDepth=uiImageBase;
		ddsHead.dwCaps=DDSCAPS_TEXTURE;
		ddsHead.dwCaps2=DDSCAPS2_VOLUME;

		//define pixel format L8
		ddsHead.ddspf.dwSize=sizeof(DDS_PIXELFORMAT);//32
		ddsHead.ddspf.dwFlags=DDPF_LUMINANCE;//DDPF_RGB | DDPF_ALPHAPIXELS;
		ddsHead.ddspf.dwFourCC=0;
		ddsHead.ddspf.dwRGBBitCount=8;//L8
		ddsHead.ddspf.dwRBitMask=0x000000ff;
		ddsHead.ddspf.dwGBitMask=0x0;
		ddsHead.ddspf.dwBBitMask=0x0;
		ddsHead.ddspf.dwABitMask=0x0;


		std::ofstream out(filename);

		static const DWORD dwMagic = 0x20534444;

		out.write((char*)&dwMagic, sizeof(DWORD));
		out.write((char*)&ddsHead, sizeof(DDS_HEADER));
		out.write((char*) pbyData, uiRawSize);

		out.close();

		delete pbyData;

		return true;
	}
};
